const Dicer = require("dicer");
const querystring = require("querystring");
const contentTypeParser = require("content-type");
const util = require("../utils");

const parseContentType = (contentTypeHeader) => {
	if (!contentTypeHeader) {
		return { contentType: null };
	}
	const { type, parameters } = contentTypeParser.parse(contentTypeHeader);
	return {
		contentType: type.replace(/\+$/, ""),
		parameters,
	};
};

module.exports = (req, res, next) => {
	req.bodyChunks = [];

	req.on("data", (chunk) => {
		req.bodyChunks.push(chunk);
	});

	req.on("end", () => {
		req.rawBody = Buffer.concat(req.bodyChunks);
		req.body = req.rawBody.toString("utf8");
		req.bodySize = req.rawBody.length;
		req.jsonBody = null;
		req.formBody = null;
		req.contentType = null;
		req.multiPartSimple = {};

		// parse Content-Type
		const contentTypeHeader = req.headers["content-type"];
		const { contentType, parameters } = parseContentType(contentTypeHeader);

		if (contentType) {
			req.contentType = contentType;
		}

		// create HAR Object
		req.har = util.createHar(req);
		req.simple = util.createSimpleHar(req);

		switch (req.contentType) {
			case "application/json":
				try {
					req.jsonBody = JSON.parse(req.body);
				} catch (exception) {}

				next();
				break;

			case "application/x-www-form-urlencoded":
				req.formBody = querystring.parse(req.body);

				// update HAR objects
				req.simple.postData.params = req.formBody;
				req.har.log.entries[0].request.postData.params = util.objectToArray(
					req.formBody,
				);

				next();
				break;

			case "multipart/mixed":
			case "multipart/related":
			case "multipart/form-data":
			case "multipart/alternate": {
				req.multiPartData = [];
				req.multiPartParams = [];

				// parse a file upload
				const dice = new Dicer({
					boundary: parameters.boundary,
				});

				dice.on("part", (part) => {
					part.on("data", (data) => {
						req.multiPartData.push(data.toString("utf8"));
					});

					part.on("header", (headers) => {
						const param = {};

						if (headers["content-disposition"]) {
							const disposition = contentTypeParser.parse(
								headers["content-disposition"][0].replace(
									"form-data",
									"form-data/text",
								) || "form-data/text",
							);

							param.name = disposition.parameters.name;

							if (disposition.parameters.filename) {
								param.fileName = disposition.parameters.filename;
							}
						}

						if (headers["content-type"]) {
							const { contentType: partContentType } = parseContentType(
								headers["content-type"][0] || "application/octet-stream",
							);

							param.contentType = partContentType;
						}

						req.multiPartParams.push(param);
					});
				});

				dice.on("finish", () => {
					// createa a new simple object param
					req.multiPart = req.multiPartParams.map((param, index) => {
						// append value to pair
						param.value = req.multiPartData[index];

						switch (typeof req.multiPartSimple[param.name]) {
							case "undefined":
								req.multiPartSimple[param.name] = param.value;
								break;

							// array
							case "object":
								req.multiPartSimple[param.name].push(param.value);
								break;

							case "string":
								// this exists? must be an array, make it so
								req.multiPartSimple[param.name] = [
									req.multiPartSimple[param.name],
								];
								req.multiPartSimple[param.name].push(param.value);
								break;
						}

						return param;
					});

					// update HAR objects
					req.simple.postData.params = req.multiPartSimple
						? req.multiPartSimple
						: [];
					req.har.log.entries[0].request.postData.params = req.multiPart
						? req.multiPart
						: [];

					next();
				});

				dice.write(req.body);
				break;
			}

			default:
				next();
		}
	});
};
